package demo.jwt;

import demo.model.JwtUser;
import io.jsonwebtoken.*;
import io.jsonwebtoken.jackson.io.JacksonDeserializer;
import io.jsonwebtoken.lang.Maps;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecurityException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtTokenUtil {
    private static final Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);

    private static final String USER_INFO_KEY = "USER_INFO";
    // 运行时生成新的SecretKey
//    private static final SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512); //or HS384 or HS256
    private static final SecretKey secretKey;

    static {
        // todo 开发阶段，用固定的key
        String strKey = "mLatJ6KdVkYycIKa2bYVoFG19SHnKbGaO6RpCYXqqjoNfqqlntR/mHhBYWK63dGlPGdxZvSMSo/pdF3hkOFaYg==";
        byte[] codes = Base64.getDecoder().decode(strKey);
        secretKey = Keys.hmacShaKeyFor(codes);
    }

    @Value("${jwt.expiration:864_00_000}")
    private static int jwtExpirationMs;

    public static String generateJwtToken(Authentication authentication) {
        JwtUser user = new JwtUser((UserDetails) authentication.getPrincipal());
        return generateJwtToken(user);
    }

    private static String generateJwtToken(JwtUser user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(USER_INFO_KEY, user);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject((user.getUsername()))
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
                .signWith(secretKey)
                .compact();
    }

    public static String getUsernameFromToken(Jws<Claims> jws) {
        return jws.getBody().getSubject();
    }

    public static JwtUser getUserFromToken(Jws<Claims> jws) {
        Claims claims = jws.getBody();
        return claims.get(USER_INFO_KEY, JwtUser.class);
    }

    public static Jws<Claims> validateJwtToken(String token) {
        try {
            return parseToken(token);
        } catch (SecurityException e) {
            logger.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            logger.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            logger.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            logger.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims string is empty: {}", e.getMessage());
        }
        return null;
    }

    private static Jws<Claims> parseToken(String token) {
        return Jwts.parserBuilder()
                // 解析 JWT 的服务器与创建 JWT 的服务器的时钟不一定完全同步，此设置允许两台服务器最多有 3 分钟的时差
                .setAllowedClockSkewSeconds(180L)
                .setSigningKey(secretKey)
                // 默认情况下 JJWT 只能解析 String, Date, Long, Integer, Short and Byte 类型，如果需要解析其他类型则需要配置 JacksonDeserializer
                .deserializeJsonWith(new JacksonDeserializer(Maps.of(USER_INFO_KEY, JwtUser.class).build()))
                .build()
                .parseClaimsJws(token);
    }
}
